﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using Microsoft.Practices.Unity;

namespace Commands.Commands
{
    internal class ParametersHelper
    {
        public ParametersHelper(string defaultDownloadFolder, string defaultPassword)
        {
            _defaultDownloadFolder = defaultDownloadFolder;
            _defaultPassword = defaultPassword;
        }
        
        public IEnumerable<object> CreateMethodParameters(IEnumerable<ParameterInfo> methodParameters, Command command,
            IUnityContainer container)
        {
            if (command.Parameters.HasParametersWithoutNames())
                return _CreateMethodParametersWithoutNames(methodParameters, command, container);
            else
                return _CreateMethodParametersWithNames(methodParameters, command, container);
        }

        private IEnumerable<object> _CreateMethodParametersWithoutNames(
            IEnumerable<ParameterInfo> methodParameters, Command command, IUnityContainer container)
        {
            var parameters = new List<object>();

            var values = new Queue<string>(command.Parameters.Select(x => x.Value));

            foreach (var methodParameterInfo in methodParameters)
            {
                object parameterValue = null;

                // If method parameter value must be taken from container.
                if (container.IsRegistered(methodParameterInfo.ParameterType))
                    parameterValue = container.Resolve(methodParameterInfo.ParameterType);
                // If its common parameter.
                else
                {
                    var parameterAttribute = methodParameterInfo.GetParameterAttribute();

                    string value = null;
                    if(values.Count > 0)
                        value = values.Dequeue();

                    // Cast value to proper type.
                    parameterValue = _ChangeType(value, parameterAttribute.Type);
                }

                parameters.Add(parameterValue);
            }

            return parameters;
        }

        /// <summary>
        /// Create parameters array for invoking method.
        /// </summary>
        /// <param name="method">Method to invoke.</param>
        /// <param name="command">Command representing method.</param>
        /// <param name="container">Container with special method parameters.</param>
        /// <returns>Collection of method parameters.</returns>
        /// <exception cref="ArgumentException">Thrown in case if number of method parameters and
        /// method attributes which describing this parameters are not the same.</exception>
        private IEnumerable<object> _CreateMethodParametersWithNames(
            IEnumerable<ParameterInfo> methodParameters, Command command, IUnityContainer container)
        {
            // Get all method parameters attributes.
            var allParameterAttributes = ParameterAttribute.GetMetodParameters(methodParameters);

            // Check that we have one and only one required parameter - 
            // then value of this parameter can be value of default parameter.
            var isOnlyOneRequiredParameter = allParameterAttributes.Count(x => x.IsRequired) == 1;

            var parameters = new List<object>();

            foreach (var methodParameterInfo in methodParameters)
            {
                object parameterValue = null;

                // If method parameter value must be taken from container.
                if (container.IsRegistered(methodParameterInfo.ParameterType))
                    parameterValue = container.Resolve(methodParameterInfo.ParameterType);
                // If its common parameter.
                else
                {
                    // Get parameter attribute by index.
                    var parameterAttribute = methodParameterInfo.GetParameterAttribute();

                    Debug.Assert(parameterAttribute != null);

                    // Default parameters types for special attributes
                    if (parameterAttribute is MailAddressParameterAttribute)
                    {
                        parameterValue = command.Parameters.GetMailAddress();
                        // If no such parameter - use sender's mail address.
                        if (parameterValue == null)
                            parameterValue = command.SenderAddress;
                    }
                    else if (parameterAttribute is DirectoryParameterAttribute)
                    {
                        parameterValue = command.Parameters.GetDirectoryParameter();
                        // If no such parameter and it wasn't required - use default value.
                        if (parameterValue == null && parameterAttribute.IsRequired)
                            parameterValue = _defaultDownloadFolder;
                    }
                    else if (parameterAttribute is BoolParameterAttribute)
                    {
                        var parameterPresent = command.Parameters.FirstOrDefault(x => parameterAttribute.Names.Contains(x.Name));
                        // If parameter present - set value to true, and to false otherwise.
                        parameterValue = parameterPresent != null;
                    }
                    else if (parameterAttribute is PasswordParameter)
                    {
                        parameterValue = command.Parameters.GetPassword();
                        if (parameterValue == null && parameterAttribute.IsRequired)
                            parameterValue = _defaultPassword;
                    }
                    else if (parameterAttribute is FolderWithSavedAttachesParameter)
                    {
                        parameterValue = command.FolderWithSavedAttachesPath;
                    }
                    // If it is non special parameter.
                    else
                    {
                        string parameterValueAsString;
                        // Check that we can apply default parameter value.
                        if (parameterAttribute.IsRequired && isOnlyOneRequiredParameter &&
                            command.Parameters.DefaultParameter != null)
                        {
                            parameterValueAsString = command.Parameters.DefaultParameter;
                        }
                        // Get parameter value by name.
                        else
                        {
                            var parameter = command.Parameters.FirstOrDefault(
                                x => parameterAttribute.Names.Contains(x.Name));

                            if (parameter != null)
                            {
                                parameterValueAsString = parameter.Value;
                            }
                            else
                            {
                                // Check that this parameter isn't required.
                                if (parameterAttribute.IsRequired)
                                    throw new ArgumentException("Required parameter hasn't been found.");

                                parameterValueAsString = null;
                            }
                        }

                        // If we have no such parameter in list.
                        parameterValue = _ChangeType(parameterValueAsString, parameterAttribute.Type);
                    }
                }

                parameters.Add(parameterValue);
            }

            return parameters;
        }

        private object _ChangeType(string valueAsString, Type resultType)
        {
            return valueAsString == null ?
                            _GetDefault(resultType) :
                            Convert.ChangeType(valueAsString, resultType);
        }

        private static object _GetDefault(Type type)
        {
            return type.IsValueType ? Activator.CreateInstance(type) : null;
        }

        private string _defaultDownloadFolder;
        private string _defaultPassword;
    }
}
